[[...routeSlug]].tsx 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. import { IS_PLATFORM, LOCAL_STORAGE_KEYS, useParams } from 'common'
  2. import { AlertTriangleIcon } from 'lucide-react'
  3. import { NextPage } from 'next'
  4. import { useRouter } from 'next/router'
  5. import { useEffect, useState } from 'react'
  6. import {
  7. Alert,
  8. AlertDescription,
  9. AlertTitle,
  10. Select,
  11. SelectContent,
  12. SelectItem,
  13. SelectTrigger,
  14. SelectValue,
  15. } from 'ui'
  16. import { ShimmeringLoader } from 'ui-patterns'
  17. import {
  18. Header,
  19. LoadingCardView,
  20. NoOrganizationsState,
  21. } from '@/components/interfaces/Home/ProjectList/EmptyStates'
  22. import { ProjectList } from '@/components/interfaces/Home/ProjectList/ProjectList'
  23. import { HomePageActions } from '@/components/interfaces/HomePageActions'
  24. import { PageLayout } from '@/components/layouts/PageLayout/PageLayout'
  25. import { ScaffoldContainer, ScaffoldSection } from '@/components/layouts/Scaffold'
  26. import { useOrganizationsQuery } from '@/data/organizations/organizations-query'
  27. import { useLocalStorageQuery } from '@/hooks/misc/useLocalStorage'
  28. import { withAuth } from '@/hooks/misc/withAuth'
  29. // [Joshen] I'd say we don't do route validation here, this page will act more
  30. // like a proxy to the project specific pages, and we let those pages handle
  31. // any route validation logic instead
  32. const GenericProjectPage: NextPage = () => {
  33. const router = useRouter()
  34. const { slug } = useParams()
  35. const { routeSlug, ...queryParams } = router.query
  36. const [lastVisitedOrgSlug] = useLocalStorageQuery(
  37. LOCAL_STORAGE_KEYS.LAST_VISITED_ORGANIZATION,
  38. ''
  39. )
  40. const {
  41. data: organizations = [],
  42. isSuccess: isSuccessOrganizations,
  43. isPending: isLoadingOrganizations,
  44. isError: isErrorOrganizations,
  45. } = useOrganizationsQuery({
  46. enabled: IS_PLATFORM,
  47. })
  48. const [selectedSlug, setSlug] = useState<string>(
  49. slug || lastVisitedOrgSlug || organizations[0]?.slug
  50. )
  51. const selectedOrganization = organizations.find((x) => x.slug === selectedSlug)
  52. const query = Object.keys(queryParams).length
  53. ? `?${new URLSearchParams(queryParams as Record<string, string>)}`
  54. : undefined
  55. const urlRewriterFactory = (slug: string | string[] | undefined) => {
  56. return (projectRef: string) => {
  57. const hash = location.hash
  58. if (!Array.isArray(slug)) {
  59. return [`/project/${projectRef}`, query, hash].filter(Boolean).join('')
  60. }
  61. const slugPath = slug.join('/')
  62. return [`/project/${projectRef}/${slugPath}`, query, hash].filter(Boolean).join('')
  63. }
  64. }
  65. useEffect(() => {
  66. if (!!lastVisitedOrgSlug) {
  67. setSlug(lastVisitedOrgSlug)
  68. } else if (isSuccessOrganizations) {
  69. setSlug(organizations[0]?.slug)
  70. }
  71. // eslint-disable-next-line react-hooks/exhaustive-deps
  72. }, [lastVisitedOrgSlug, isSuccessOrganizations])
  73. return (
  74. <div className="h-screen flex flex-col">
  75. <Header />
  76. <PageLayout className="grow min-h-0" title="Select a project to continue">
  77. <ScaffoldContainer className="grow flex flex-col gap-y-4">
  78. <ScaffoldSection isFullWidth className="py-0">
  79. <div className="flex items-center gap-x-2">
  80. {isLoadingOrganizations ? (
  81. <ShimmeringLoader className="w-60 py-0 h-[26px]" />
  82. ) : (
  83. <Select value={selectedSlug} onValueChange={setSlug}>
  84. <SelectTrigger size="tiny" className="w-60 truncate">
  85. <div className="flex items-center gap-x-2">
  86. <p className="text-xs text-foreground-light">Organization:</p>
  87. <SelectValue placeholder="Select an organization" />
  88. </div>
  89. </SelectTrigger>
  90. <SelectContent className="col-span-8">
  91. {organizations.map((org) => (
  92. <SelectItem key={org.slug} value={org.slug} className="text-xs">
  93. {org.name}
  94. </SelectItem>
  95. ))}
  96. </SelectContent>
  97. </Select>
  98. )}
  99. <HomePageActions hideNewProject />
  100. </div>
  101. </ScaffoldSection>
  102. <ScaffoldSection isFullWidth className="py-0">
  103. {isLoadingOrganizations ? (
  104. <LoadingCardView />
  105. ) : isErrorOrganizations ? (
  106. <Alert variant="warning">
  107. <AlertTriangleIcon />
  108. <AlertTitle>Failed to load your Briven organizations</AlertTitle>
  109. <AlertDescription>Try refreshing the page</AlertDescription>
  110. </Alert>
  111. ) : organizations.length === 0 ? (
  112. <NoOrganizationsState />
  113. ) : !!selectedOrganization ? (
  114. <ProjectList
  115. organization={selectedOrganization}
  116. rewriteHref={urlRewriterFactory(routeSlug)}
  117. />
  118. ) : null}
  119. </ScaffoldSection>
  120. </ScaffoldContainer>
  121. </PageLayout>
  122. </div>
  123. )
  124. }
  125. export default withAuth(GenericProjectPage)